package com.rockwell.sniffyhunter.views;

import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.imgproc.Imgproc;

import com.rockwell.sniffyhunter.R;
import com.rockwell.sniffyhunter.RobotActivity;
import com.rockwell.sniffyhunter.SettingsActivity;
import com.rockwell.sniffyhunter.model.ColorBlobDetector;
import com.rockwell.sniffyhunter.model.ColorBlobInfo;
import com.rockwell.sniffyhunter.utils.DetectionIndex;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;

@SuppressLint("UseSparseArrays") 
public class MazeCameraView extends RelativeLayout implements OnTouchListener, CvCameraViewListener2, SensorEventListener, OnItemSelectedListener  {

	private static final String  TAG = "MazeCameraView::RelativeLayout";

	private OnOrientationChangeListener m_OrientationListener;
	
	private boolean				 m_TeachMode = false;
    private boolean              m_IsColorSelected = false;
    private Mat                  m_Rgba;
    private Scalar               m_labelRgba;
    private Scalar               m_BlobColorHsv;
    private Map<Integer, ColorBlobDetector> m_colorDetectors;
    private Map<Integer, String> m_colorDetectorNames;
    private Mat                  m_Spectrum;
    private Size                 SPECTRUM_SIZE;
    
    private CameraBridgeViewBase m_OpenCvCameraView;
    private TextView 			 m_CompassValueView;
    private TextView 			 m_TeachPromptView;
    private Spinner 			 m_TeachIndexSpinner;
    
    // Compass parameters
    private SensorManager 		 m_SensorManager;
    private Sensor 				 m_Gsensor;
    private Sensor 				 m_Msensor;
    private float[] 			 mGravity = new float[3];
    private float[] 			 mGeomagnetic = new float[3];
    private float 				 azimuth = 0f;
    private float 				 currectAzimuth = 0;
    private float[]				 m_Orientation = new float[3];
    private int 				 m_Count;
    private int		 			 m_teachIndex = DetectionIndex.TREASURE;
    private Map<Integer, OnColorBlobDetectListener> m_colorDetectListeners;

    private BaseLoaderCallback  m_LoaderCallback = new BaseLoaderCallback(this.getContext()) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");
                    m_OpenCvCameraView.enableView();
                } 
                break;
                default:
                {
                    super.onManagerConnected(status);
                } 
                break;
            }
        }
    };

	public MazeCameraView(Context context) {
		super(context);
		init(context);
	}
	
	public MazeCameraView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	
	public MazeCameraView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}
	
	public void setOrientationChangeListener(OnOrientationChangeListener l) {
		m_OrientationListener = l;
	}
	
	public void setDetectionListener(Map<Integer, OnColorBlobDetectListener> listeners) {
		m_colorDetectListeners = listeners;
	}

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		super.onLayout(arg0, arg1, arg2, arg3, arg4);
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	@Override
    public void onSensorChanged(SensorEvent event) {
        final float alpha = 0.97f;

        synchronized (this) {
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                    mGravity[0] = event.values[0];
                    mGravity[1] = event.values[1];
                    mGravity[2] = event.values[2];
            }

            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
                    mGeomagnetic[0] = event.values[0];
                    mGeomagnetic[1] = event.values[1];
                    mGeomagnetic[2] = event.values[2];
            }

            float R[] = new float[9];
            float I[] = new float[9];
            boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
            
            if (success) {
        		float[] data = new float[3];
                SensorManager.getOrientation(R, data);

                data[0] = (float) Math.toDegrees(data[0]);
                data[0] = (data[0] + 360) % 360;
                data[1] = (float)Math.toDegrees(data[1]);
	        	data[2] = (float)Math.toDegrees(data[2]);
	        	
	        	System.arraycopy(data, 0, m_Orientation, 0, 3);
	        	
	        	DecimalFormat displayFormat = new DecimalFormat("#.#");
                
                String sValues = "Azimuth:\t" + String.valueOf(displayFormat.format(m_Orientation[0])) +
                         		 "\nPitch:\t" + String.valueOf(displayFormat.format(m_Orientation[1])) +
                         		 "\nRoll:\t" + String.valueOf(displayFormat.format(m_Orientation[2]));

	            Log.d("Compass", sValues);
	            m_CompassValueView.setText(sValues);
	            
	            if (m_OrientationListener != null)
	            	m_OrientationListener.onOrientationChanged(m_Orientation);
            }
        }
    }

	@SuppressLint("UseSparseArrays") @SuppressWarnings("serial")
	@Override
	public void onCameraViewStarted(int width, int height) {
        m_Rgba = new Mat(height, width, CvType.CV_8UC4);
        
        m_colorDetectors = new HashMap<Integer, ColorBlobDetector>() {{
        		put(DetectionIndex.TREASURE, new ColorBlobDetector(new Scalar(0, 255, 0), DetectionIndex.TREASURE, m_colorDetectorNames.get(DetectionIndex.TREASURE)));
        		put(DetectionIndex.OBSTACLE, new ColorBlobDetector(new Scalar(0, 0, 255), DetectionIndex.OBSTACLE, m_colorDetectorNames.get(DetectionIndex.OBSTACLE)));
        		put(DetectionIndex.MAZE1, new ColorBlobDetector(new Scalar(255, 0, 0), DetectionIndex.MAZE1, m_colorDetectorNames.get(DetectionIndex.MAZE1)));
        		put(DetectionIndex.MAZE2, new ColorBlobDetector(new Scalar(255, 0, 0), DetectionIndex.MAZE2, m_colorDetectorNames.get(DetectionIndex.MAZE2)));
        		put(DetectionIndex.MAZE3, new ColorBlobDetector(new Scalar(255, 0, 0), DetectionIndex.MAZE3, m_colorDetectorNames.get(DetectionIndex.MAZE3)));
        		put(DetectionIndex.DROPZONE, new ColorBlobDetector(new Scalar(255, 0, 0), DetectionIndex.DROPZONE, m_colorDetectorNames.get(DetectionIndex.DROPZONE)));
        		put(DetectionIndex.MAZE_EXIT, new ColorBlobDetector(new Scalar(255, 0, 0), DetectionIndex.MAZE_EXIT, m_colorDetectorNames.get(DetectionIndex.MAZE_EXIT)));
        };};

        if (m_colorDetectListeners != null) {
	        for (Map.Entry<Integer, OnColorBlobDetectListener> listener : m_colorDetectListeners.entrySet()) {
				m_colorDetectors.get(listener.getKey()).setDetectorListener(listener.getValue());
			}
        }
        
        m_Spectrum = new Mat();
        m_labelRgba = new Scalar(255);
        m_BlobColorHsv = new Scalar(255);
        SPECTRUM_SIZE = new Size(200, 64);
        
        deserializeDetectionColors();
        
        m_IsColorSelected = true;
    }

	@Override
	public void onCameraViewStopped() {
		m_Rgba.release();
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
        int cols = m_Rgba.cols();
        int rows = m_Rgba.rows();

        int xOffset = (m_OpenCvCameraView.getWidth() - cols) / 2;
        int yOffset = (m_OpenCvCameraView.getHeight() - rows) / 2;

        int x = (int)event.getX() - xOffset;
        int y = (int)event.getY() - yOffset;

        Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");

        if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return false;

        Rect touchedRect = new Rect();

        touchedRect.x = (x>4) ? x-4 : 0;
        touchedRect.y = (y>4) ? y-4 : 0;

        touchedRect.width = (x+4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x;
        touchedRect.height = (y+4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y;

        Mat touchedRegionRgba = m_Rgba.submat(touchedRect);

        Mat touchedRegionHsv = new Mat();
        Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);

        // Calculate average color of touched region
        m_BlobColorHsv = Core.sumElems(touchedRegionHsv);
        int pointCount = touchedRect.width*touchedRect.height;
        for (int i = 0; i < m_BlobColorHsv.val.length; i++)
            m_BlobColorHsv.val[i] /= pointCount;

        serializeDetectionColor(m_teachIndex);

        touchedRegionRgba.release();
        touchedRegionHsv.release();

        return false; // don't need subsequent touch events
    }
	
	private void serializeDetectionColor(int index) {
		
		if (index < m_colorDetectors.size()) {
			
			m_colorDetectors.get(index).setHsvColor(m_BlobColorHsv);
	        Imgproc.resize(m_colorDetectors.get(index).getSpectrum(), m_Spectrum, SPECTRUM_SIZE);
	
	        //m_IsColorSelected = true;
	        
	        SettingsActivity.serializeDetectionColor(getContext(), m_BlobColorHsv, index);
		}
	}
	
	private void deserializeDetectionColors() {
		Context context = getContext();
		
		for (ColorBlobDetector detector : m_colorDetectors.values()) {
			m_BlobColorHsv = SettingsActivity.deserializeDetectionColor(context, detector.getDetectionIndex());
			detector.setHsvColor(m_BlobColorHsv);
	        Imgproc.resize(detector.getSpectrum(), m_Spectrum, SPECTRUM_SIZE);
		}
	}
	
	private void detect() {
		for (ColorBlobDetector detector : m_colorDetectors.values()) {
			if (detector.m_detectionIndex == DetectionIndex.OBSTACLE)
				continue;

			detector.detect(m_Rgba);
		}
	}

	public void setTeachMode(boolean teachMode) {
		m_TeachMode = teachMode;
		
		if (m_TeachMode) {
        	m_OpenCvCameraView.setOnTouchListener(MazeCameraView.this);
        	m_TeachIndexSpinner.setOnItemSelectedListener(this);
        	setTeachPrompt(m_teachIndex);
		}
		else {
			m_OpenCvCameraView.setOnTouchListener(null);
			m_TeachIndexSpinner.setVisibility(GONE);
			m_TeachPromptView.setVisibility(GONE);
			setTeachPrompt(-1);
		}
	}
	
	private void setTeachPrompt(int index) {
		if (index != -1) {
			String value = "Please Teach: " + m_colorDetectorNames.get(index);
			m_TeachPromptView.setText(value);
		}
		else {
			m_TeachPromptView.setText("");
		}
	}
	
	public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        m_Rgba = inputFrame.rgba();

        if (m_IsColorSelected) {
        	detect();

            Mat colorLabel = m_Rgba.submat(4, 68, 4, 68);
            colorLabel.setTo(m_labelRgba);

            Mat spectrumLabel = m_Rgba.submat(4, 4 + m_Spectrum.rows(), 70, 70 + m_Spectrum.cols());
            m_Spectrum.copyTo(spectrumLabel);
        }

        return m_Rgba;
    }

    private Scalar converScalarHsv2Rgba(Scalar hsvColor) {
        Mat pointMatRgba = new Mat();
        Mat pointMatHsv = new Mat(1, 1, CvType.CV_8UC3, hsvColor);
        Imgproc.cvtColor(pointMatHsv, pointMatRgba, Imgproc.COLOR_HSV2RGB_FULL, 4);

        return new Scalar(pointMatRgba.get(0, 0));
    }
	
	@SuppressWarnings("serial")
	private void init(Context context) {
		Log.i(TAG, "called init");
		
		LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate(R.layout.maze_camera_layout, this);
		
        m_CompassValueView = (TextView) findViewById(R.id.compass_values);
        m_TeachPromptView = (TextView) findViewById(R.id.teach_prompt);
        m_TeachIndexSpinner = (Spinner) findViewById(R.id.spinner_teach_index);
        m_OpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.opencv_view);
        m_OpenCvCameraView.setCvCameraViewListener(this);
        
        m_SensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE);
        m_Gsensor = m_SensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        m_Msensor = m_SensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        
        m_colorDetectorNames = new HashMap<Integer, String>() {{
	    		put(DetectionIndex.TREASURE, "T");
	    		put(DetectionIndex.OBSTACLE, "O");
	    		put(DetectionIndex.MAZE1, "M1");
	    		put(DetectionIndex.MAZE2, "M2");
	    		put(DetectionIndex.MAZE3, "M3");
	    		put(DetectionIndex.DROPZONE, "D");
	    		put(DetectionIndex.MAZE_EXIT, "ME");
	    };};
	}
	
	public void onActivated() {
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_9, getContext(), m_LoaderCallback);

        m_SensorManager.registerListener(this, m_Gsensor, SensorManager.SENSOR_DELAY_NORMAL);
        m_SensorManager.registerListener(this, m_Msensor, SensorManager.SENSOR_DELAY_NORMAL);
    }
	
	public void onDeactivated() {
        if (m_OpenCvCameraView != null)
            m_OpenCvCameraView.disableView();
        
        m_SensorManager.unregisterListener(this);
    }
	
	@Override
	public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
		m_teachIndex = position;
		setTeachPrompt(m_teachIndex);
	}

	@Override
	public void onNothingSelected(AdapterView<?> parent) {
	}
}
